<script setup>
import { ref, onMounted, inject } from 'vue';
import { ElMessage } from 'element-plus';
import { useRoute } from 'vue-router';
import { BREADCRUMBS } from '@/plugins/global';

const axios = inject('axios'), route = useRoute();

// ---- 生命钩子：在组件完全挂载时执行。
onMounted(async () => {
  BREADCRUMBS.splice(0, BREADCRUMBS.length,
    { label: '员工列表', path: '/home/staff_list' },
    { label: '员工权限', path: '/home/staff_authority' }
  );
  // ---- “同步”查询角色集/获取权限树/获取员工权限集。
  await queryRoles();
  await getAuthorityTree();
  await getStaffAuthorities();
});

// ---- 查询角色集：查询所有角色及其权限，并格式化为列表&映射，用于展示选择及权限分配。
const roles = ref([]), roleMap = {};
const queryRoles = async () => {
  const response = await axios.get('/api/cms-web/role/queries?roleCarryAuthorities=true');
  if (response.data.success) {
    const data = response.data.data.data;
    roles.value = data;
    for (let role of data) {
      roleMap[role.roleCode] = role.authorities;
    }
  } else {
    ElMessage.error(response.data.message);
  }
}

// ---- 获取权限树：获取所有权限，并展示为树。
const authorityTree = ref([]);
const staffAuthorityTreeProps = {
  "label": "name",
  "children": "childAuthorities",
  "diabled": "diabled"
}
const getAuthorityTree = async () => {
  const response = await axios.get('/api/cms-web/authority/get/tree');
  if (response.data.success) {
    authorityTree.value.push(response.data.data);
  } else {
    ElMessage.error(response.data.message);
  }
}

// ---- 获取员工权限集：获取指定员工的权限&角色，并选中相关的角色&权限节点。
const staffAuthorityMap = ref({}), staffRoleMap = ref({});
const staffOldRoleCodes = ref([]), staffNewRoleCodes = ref([]);
const getStaffAuthorities = async () => {
  const response = await axios.get(`/api/cms-web/staff/get/authorities?staffAccount=${route.params.staffAccount}`);
  if (!response.data.success) {
    ElMessage.error(response.data.message);
    return;
  }
  // ---- 如果员工不存在权限数据，重置权限树的状态。
  staffAuthorityMap.value = {};
  staffRoleMap.value = {};
  staffOldRoleCodes.value = [];
  staffNewRoleCodes.value = [];
  const data = response.data.data;
  if (data == null) {
    allChooseAuthorityNodes();
    return;
  }
  // ---- 如果员工存在权限数据，进行格式化，并全局选择权限节点集。
  for (let authority of data.authorities) {
    staffAuthorityMap.value[authority.authorityCode] = authority.expireDatetimeStr;
  }
  for (let role of data.roles) {
    const roleCode = role.roleCode;
    staffOldRoleCodes.value.push(roleCode);
    staffNewRoleCodes.value.push(roleCode);
    staffRoleMap.value[roleCode] = role.expireDatetimeStr;
  }
  allChooseAuthorityNodes();
}

// ---- 全局选择权限节点集：即根据员工全局权限映射选中/取消相应的权限节点。
const staffAuthorityTreeRef = ref(null);
const staffRoleAuthorityCodes = ref(new Set());
const staffAllAuthorityMap = ref({});
const isChooseAuthorityNode = ref(false);
const allChooseAuthorityNodes = () => {
  isChooseAuthorityNode.value = false;
  // ---- 取消所有已选中权限节点的选中&禁用状态以重置权限树状态（但不重置数据）。
  const authorityNodes = staffAuthorityTreeRef.value.getCheckedNodes(false);
  for (let authorityNode of authorityNodes) {
    staffAuthorityTreeRef.value.setChecked(authorityNode.authorityCode, false, false);
    authorityNode.disabled = false;
  }
  // ---- 遍历员工角色映射：汇总&格式化为员工角色权限编码集&员工全局权限映射。
  staffRoleAuthorityCodes.value = new Set();
  staffAllAuthorityMap.value = {};
  for (const [roleCode, roleExpireDatetimeStr] of Object.entries(staffRoleMap.value)) {
    for (const roleAuthority of roleMap[roleCode]) {
      const roleAuthorityCode = roleAuthority.authorityCode;
      staffRoleAuthorityCodes.value.add(roleAuthorityCode);
      // ---- 将当前角色权限对应的权限节点复选框设置为禁用，因为角色权限对应的权限节点不能通过
      // 点击复选框的形式来选中/取消。
      const authorityNode = staffAuthorityTreeRef.value.getNode(roleAuthorityCode);
      authorityNode.data.disabled = true;
      // ---- 取当前角色权限的过期时间与当前角色的过期时间进行比较，取较小值作为当前角色权限在
      // 当前角色下的过期时间。
      let roleAuthorityExpireDatetimeStr = minDatetime(roleExpireDatetimeStr, roleAuthority.expireDatetimeStr);
      // ---- 取当前角色权限的当前/其它角色的过期时间进行比较，取较大值作为当前角色权限在所有
      // 角色下的过期时间。
      if (Object.hasOwn(staffAllAuthorityMap.value, roleAuthorityCode)) {
        const expireDatetimeStr = staffAllAuthorityMap.value[roleAuthorityCode];
        roleAuthorityExpireDatetimeStr = maxDatetime(roleAuthorityExpireDatetimeStr, expireDatetimeStr);
      }
      staffAllAuthorityMap.value[roleAuthorityCode] = roleAuthorityExpireDatetimeStr;
    }
  }
  // ---- 遍历员工权限映射：汇总&格式化为权限编码集&员工全局权限映射。角色权限过期时间的优先级
  // 高于直接权限过期时间的优先级，因此如果当前权限是角色权限，那么其作为直接权限的过期时间直接
  // 作废。
  for (const [authorityCode, authorityExpireDatetimeStr] of Object.entries(staffAuthorityMap.value)) {
    if (!Object.hasOwn(staffAllAuthorityMap.value, authorityCode)) {
      staffAllAuthorityMap.value[authorityCode] = authorityExpireDatetimeStr;
    }
  }
  // ---- 选中直接/角色权限对应的权限节点。
  staffAuthorityTreeRef.value.setCheckedKeys(Object.keys(staffAllAuthorityMap.value));
}

// ---- 选择角色：选中/取消角色。
const chooseRole = () => {
  if (staffNewRoleCodes.value.length > staffOldRoleCodes.value.length) {
    // ---- 员工角色编码增加，说明有新角色被选中。获取选中的角色，将之插入员工角色映射中。
    let roleCode = staffNewRoleCodes.value.filter(code => !staffOldRoleCodes.value.includes(code))[0];
    staffRoleMap.value = { ...staffRoleMap.value, [roleCode]: null };
  } else {
    // ---- 员工角色编码减少，说明有旧角色被取消。获取取消的角色，将之从员工角色映射中移除。
    let roleCode = staffOldRoleCodes.value.filter(code => !staffNewRoleCodes.value.includes(code))[0];
    delete staffRoleMap.value[roleCode]
  }
  staffOldRoleCodes.value = staffNewRoleCodes.value;
  allChooseAuthorityNodes();
}

// ---- 选择权限节点：即通过直接点击节点复选框来选中/取消权限节点。
const chooseAuthorityNode = (data, checked, indeterminate) => {
  // ---- 如果权限节点是因为角色选择而选中/取消，那么直接返回不触发。
  if (!isChooseAuthorityNode.value) {
    return;
  }
  let authorityCode = data.authorityCode, index;
  if (checked) {
    // ---- 在直接选中当前权限节点的同时直接选中其所有父节点，确保被所有被直接选中的权限节点可
    // 以组成完整的权限树。
    staffAuthorityMap.value = { ...staffAuthorityMap.value, [authorityCode]: null };
    staffAllAuthorityMap.value = { ...staffAllAuthorityMap.value, [authorityCode]: null };
    while ((index = authorityCode.lastIndexOf('/')) != -1) {
      authorityCode = authorityCode.substring(0, index)
      // ---- 如果在循环直接选中父权限节点的过程中，发现某个父权限节点已经被“直接”选中了，那么
      // 循环就可以停止了，因此更高级的父权限节点必然已经被直接选中了。
      if (Object.hasOwn(staffAuthorityMap.value, authorityCode)) {
        break;
      }
      staffAuthorityMap.value = { ...staffAuthorityMap.value, [authorityCode]: null };
      // ---- 在为直接选中的父权限标记为全局选中时，要先确保其没有被角色选中，否则可能存在直接
      // 权限的过期时间覆盖角色权限过期时间的非法情况。
      if (!Object.hasOwn(staffAllAuthorityMap.value, authorityCode)) {
        staffAllAuthorityMap.value = { ...staffAllAuthorityMap.value, [authorityCode]: null };
      }
      staffAuthorityTreeRef.value.setChecked(authorityCode, true, false);
    }
  } else {
    // ---- 在当前节点被取消选中的同时取消子节点。由于迭代执行的原因，其所有子节点都会被取消选
    // 中。
    delete staffAuthorityMap.value[authorityCode];
    delete staffAllAuthorityMap.value[authorityCode];
    const node = staffAuthorityTreeRef.value.getNode(authorityCode);
    if (node && node.data && node.data.childAuthorities && node.data.childAuthorities.length > 0) {
      for (let childAuthority of data.data.childAuthorities) {
        staffAuthorityTreeRef.value.setChecked(childAuthority.authorityCode, false, false);
      }
    }
  }
}

// ---- 是否权限过期：指定权限是否已经过期。
const isAuthorityExpire = (authorityCode) => {
  let flag = false;
  const expireDatetimeStr = staffAllAuthorityMap.value[authorityCode];
  if (expireDatetimeStr) {
    const expireDatetime = new Date(expireDatetimeStr.replace(' ', 'T'));
    if (expireDatetime < new Date()) {
      flag = true;
    }
  }
  return flag;
}

// ---- 设置员工权限集。
const setStaffAuthorities = () => {
  // ---- 生成权限集参数。
  const authorities = [];
  for (let [authorityCode, expireDatetimeStr] of Object.entries(staffAuthorityMap.value)) {
    authorities.push({
      "authorityCode": authorityCode,
      "expireDatetimeStr": expireDatetimeStr
    });
  }
  // ---- 生成角色集参数。
  const roles = [];
  for (let [roleCode, expireDatetimeStr] of Object.entries(staffRoleMap.value)) {
    roles.push({
      "roleCode": roleCode,
      "expireDatetimeStr": expireDatetimeStr
    });
  }
  axios.post('/api/cms-web/staff/set/authorities', {
    "staffAccount": route.params.staffAccount,
    "authorities": authorities,
    "roles": roles
  }).then(response => {
    if (response.data.success) {
      ElMessage.success("设置成功！");
    } else {
      ElMessage.error(response.data.message);
    }
  });
}

// ---- 最大日期时间：对比&获取更大时间。null表示无限大。
const maxDatetime = (datetimeStr1, datetimeStr2) => {
  if (datetimeStr1 == null || datetimeStr2 == null) {
    return null;
  }
  const datetime1 = new Date(datetimeStr1.replace(' ', 'T')), datetime2 = new Date(datetimeStr2.replace(' ', 'T'));
  return datetime1 > datetime2 ? datetimeStr1 : datetimeStr2;
}

// ---- 最小日期时间：对比&获取更小时间。null表示无限大。
const minDatetime = (datetimeStr1, datetimeStr2) => {
  if (datetimeStr1 == null) {
    return datetimeStr2;
  } else if (datetimeStr2 == null) {
    return datetimeStr1;
  } else {
    const datetime1 = new Date(datetimeStr1.replace(' ', 'T')), datetime2 = new Date(datetimeStr2.replace(' ', 'T'));
    return datetime1 < datetime2 ? datetimeStr1 : datetimeStr2;
  }
}
</script>

<template>
  <div class="staff-authority-page-div-class">
    <!-- 员工角色域 -->
    <div class="staff-role-div-class">
      <div style="width: 100%;">
        <el-button type="primary" @click="setStaffAuthorities()" style="width: 100%;">设置</el-button>
      </div>
      <div class="staff-role-checkbox-group-div-class">
        <el-checkbox-group v-model="staffNewRoleCodes" class="staff-role-checkbox-group-class" @change="chooseRole()">
          <div v-for="role in roles" class="staff-role-checkbox-div-class">
            <el-checkbox :key="role.roleCode" :value="role.roleCode" :label="role.name" border style="width: 100%;" />
            <div class="staff-role-date-picker-div-class">
              <el-date-picker v-model="staffRoleMap[role.roleCode]" size="small" type="datetime"
                format="YYYY-MM-DD HH:mm:ss" value-format="YYYY-MM-DD HH:mm:ss" style="width: 170px;"
                :disabled="!staffNewRoleCodes.includes(role.roleCode)" @change="allChooseAuthorityNodes()" />
            </div>
          </div>
        </el-checkbox-group>
      </div>
    </div>
    <!-- 员工权限域 -->
    <div class="staff-authority-div-class" @mouseenter="isChooseAuthorityNode = true">
      <!-- 权限树 -->
      <!-- 权限节点的选中存在“选择/角色选择/全局选中”的概念： -->
      <!-- 选择：即通过点击节点复选框的方式来选中/取消权限节点。通过这种方式选中的权限会与员工
       建立直接关联，并只能通过再次点击节点复选框的方式来取消； -->
      <!-- 角色选择：即通过点击角色复选框的方式来选中/取消权限节点。通过这种方式选中的权限会基
       于角色而与员工建立间接关联，并只能通过再次点击角色复选框的方式来间接取消，因此被角色选中
       的节点其复选框会被禁用以防止取消； -->
      <!-- 全局选中：全局选中是指“选中&角色选中”的并集。注意！权限节点是可以被同时“选中&角色选
       中”的，但在这种情况下间接选中的优先级会高于选中的优先级。此外还值得一说的是：所有被选中/
       间接选中的权限节点都可以分别组成完整的权限树，这是两套独立但又可合作的权限赋予机制。 -->
      <el-tree ref="staffAuthorityTreeRef" :data="authorityTree" :props="staffAuthorityTreeProps"
        :expand-on-click-node="false" :check-on-click-node="true" :check-strictly="true" node-key="authorityCode"
        default-expand-all show-checkbox @check-change="chooseAuthorityNode" style="width: 100%;">
        <template #default="{ node, data }">
          <div class="staff-authority-tree-node-div-class"
            v-if="Object.hasOwn(staffAllAuthorityMap, data.authorityCode)"
            :style="{ fontWeight: 'bolder', color: isAuthorityExpire(data.authorityCode) ? '#9e0303' : '#014e1f' }"
            @click="isChooseAuthorityNode = true">
            [{{ data.authorityCode }}] - {{ node.label }}
            <el-date-picker v-model="staffAllAuthorityMap[data.authorityCode]" type="datetime"
              format="YYYY-MM-DD HH:mm:ss" value-format="YYYY-MM-DD HH:mm:ss" style="width: 170px"
              :disabled="staffRoleAuthorityCodes.has(data.authorityCode)" size="small"
              @change="staffAuthorityMap[data.authorityCode] = staffAllAuthorityMap[data.authorityCode]" />
          </div>
          <div v-else>
            [{{ data.authorityCode }}] - {{ node.label }}
          </div>
        </template>
      </el-tree>
    </div>
  </div>
</template>

<style scoped>
.staff-authority-page-div-class {
  /* ---- 网格布局：1行 - 占满高度，2列（1:3） */
  display: grid;
  grid-template-rows: 100%;
  grid-template-columns: 1fr 3.5fr;
  width: 100%;
  height: 100%;
  box-sizing: border-box;
  padding: 20px 20px 20px 20px;
}

.staff-role-div-class {
  display: flex;
  flex-direction: column;
  justify-content: flex-start;
  align-items: center;
  gap: 20px;
  border-right: 1px solid rgb(171, 164, 164);
  width: 100%;
  height: 100%;
  padding: 5px 20px 0px 0px;
}

.staff-role-checkbox-group-div-class {
  flex: 1;
  width: 100%;
  /* ---- 溢出：滚动，但隐藏滚动条，不同的浏览器隐藏滚动条有不同的配置 */
  overflow: auto;

  /* ---- Chrome/Safari */
  &::-webkit-scrollbar {
    display: none;
  }

  /* ---- IE/Edge */
  -ms-overflow-style: none;
  /* ---- Firefox */
  scrollbar-width: none;

}

.staff-role-checkbox-group-class {
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: flex-start;
  gap: 15px;
  width: 100%;
}

.staff-role-checkbox-div-class {
  position: relative;
  width: 100%;
}

.staff-role-date-picker-div-class {
  position: absolute;
  right: 15px;
  bottom: -6px;
}

.staff-authority-div-class {
  border-left: 1px solid rgb(171, 164, 164);
  width: 100%;
  height: 100%;
  padding: 0px 0px 0px 5px;
}

.staff-authority-tree-node-div-class {
  display: flex;
  flex-direction: row;
  justify-content: space-between;
  align-items: flex-end;
  width: 100%;
}
</style>
